﻿/*
 * SystemRuntime
 *
 * Default:
 *      SystemRuntime will use MEF components/services by default
 *
 *
 */

using System;
using System.Collections.Generic;
using System.ComponentModel.Composition;
using System.ComponentModel.Composition.Hosting;
using System.ComponentModel.Composition.Primitives;
using System.Configuration;
using System.Diagnostics;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Threading;
using Snowdreamist.Collection;
using Snowdreamist.ComponentModel.MEF;
using Snowdreamist.ComponentModel.Plugin;
using Snowdreamist.ComponentModel.Services;
using Snowdreamist.Diagnostics;
using Snowdreamist.Reflection;
using Snowdreamist.Runtime.Services;

namespace Snowdreamist.Runtime
{
    public class SystemRuntime : Runtime
    {
        #region Singleton

        /// <summary>
        /// Sync root
        /// </summary>
        private static readonly Object _syncRoot = new object();
        /// <summary>
        /// Current instance
        /// </summary>
        private static volatile SystemRuntime _instance = null;

        /// <summary>
        /// Current system runtime
        /// </summary>
        public static SystemRuntime Current
        {
            get
            {
                if (_instance == null)
                    lock (_syncRoot)
                        if (_instance == null)
                            _instance = new SystemRuntime();
                return _instance;
            }
        }

        protected SystemRuntime() : base(null) { }

        #endregion Singleton

        public virtual void Run()
        {
            if (Interlocked.CompareExchange(ref _running, 1, 0) == 0)
            {
                // init all
                this.InitializeAllServices();
                // load
                this.LoadLifetimeModules();
            }
            else
            {
                throw new InvalidOperationException("SystemRuntime is running");
            }
        }

        public virtual IModule LoadModule(string moduleName)
        {
            if (string.IsNullOrWhiteSpace(moduleName))
                throw new ArgumentException("Invalid moduleName");

            // get module definition
            IModuleRepository moduleRepository = null;
            if (this.TryGetService<IModuleRepository>(out moduleRepository))
            {
                var moduleDef = moduleRepository.GetModuleDefinition(moduleName);
                if (moduleDef == null)
                {
                    throw new InvalidOperationException("Module: " + moduleName + " not found");
                }
                // load
                return this.LoadModuleCore(moduleDef);
            }
            else
            {
                throw new InvalidOperationException("ModuleRepository not found");
            }
        }

        public virtual T LoadModule<T>() where T : IModule
        {
            // get module definition
            IModuleRepository moduleRepository = null;
            if (this.TryGetService<IModuleRepository>(out moduleRepository))
            {
                var moduleDef = moduleRepository.GetModuleDefinition<T>();
                if (moduleDef == null)
                {
                    throw new InvalidOperationException("Module type: " + typeof(T).FullName + " not found");
                }
                // load
                return (T)this.LoadModuleCore(moduleDef);
            }
            else
            {
                throw new InvalidOperationException("ModuleRepository not found");
            }
        }

        public virtual bool TryLoadModule(string name, out IModule module)
        {
            try
            {
                module = this.LoadModule(name);
                return true;
            }
            catch (Exception error)
            {
                // record error
                this.Log.Write(TraceEventType.Error, (int)EventID.LoadModuleFailed, "Cannot load module " + name, error);
            }
            module = null;
            return false;
        }

        public virtual bool TryLoadModule<T>(out T module) where T : IModule
        {
            try
            {
                module = this.LoadModule<T>();
                return true;
            }
            catch (Exception error)
            {
                // record error
                this.Log.Write(TraceEventType.Error, (int)EventID.LoadModuleFailed, "Cannot load module type " + typeof(T).FullName, error);
            }

            module = default(T);
            return false;
        }

        #region Service

        /// <summary>
        /// Log
        /// </summary>
        public ILog Log
        {
            get
            {
                return this.GetService<ILog>();
            }
        }

        /// <summary>
        /// Configuration
        /// </summary>
        public IConfiguration Configuration
        {
            get
            {
                return this.GetService<IConfiguration>();
            }
        }

        /// <summary>
        /// Module activator
        /// </summary>
        public IModuleActivator ModuleActivator
        {
            get
            {
                return this.GetService<IModuleActivator>();
            }
        }

        /// <summary>
        /// Module repository
        /// </summary>
        public IModuleRepository ModuleRepository
        {
            get
            {
                return this.GetService<IModuleRepository>();
            }
        }

        #endregion Service

        #region internal

        #endregion internal

        #region protected

        /// <summary>
        /// Try to load unregistered service
        /// </summary>
        /// <param name="serviceContractType"></param>
        /// <returns></returns>
        protected override object FailedToRetrieveService(Type serviceContractType)
        {
            if (serviceContractType == null)
            {
                return null;
            }
            else
            {
                // try load default service
                Func<IRuntimeService> serviceFunc = null;
                if (_necessaryServices.TryGetValue(serviceContractType, out serviceFunc))
                {
                    lock (this)
                    {
                        if (!this.ServiceExists(serviceContractType))
                        {
                            this.SetService(serviceContractType, serviceFunc());
                        }
                    }

                    if (this.ServiceExists(serviceContractType))
                    {
                        return this.GetService(serviceContractType);
                    }
                }

                return null;
            }
        }

        protected virtual IModule LoadModuleCore(IModuleDefinition moduleDefinition)
        {
            if (moduleDefinition == null)
                throw new ArgumentNullException("moduleDefinition");

            // try get from existing module
            IModule module = null;
            if (this._modules.TryGetValue(moduleDefinition.ExportType, out module))
                return module;

            // lock this module definition in order to prevent multiple - active
            lock (moduleDefinition)
            {
                IModuleActivator activator = null;

                if (this.TryGetService<IModuleActivator>(out activator))
                {
                    module = activator.Active(moduleDefinition);
                    if (module != null)
                    {
                        // save
                        this._modules.Add(moduleDefinition.ExportType, module);
                        // notify
                        this.OnModuleLoaded(moduleDefinition, module);
                        // return
                        return module;
                    }
                    else
                    {
                        throw new InvalidOperationException("ModuleActivator returns null");
                    }
                }
                else
                {
                    throw new InvalidOperationException("ModuleActivator not found");
                }
            }
        }

        /// <summary>
        /// Create new module
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <returns></returns>
        internal virtual ModuleRuntime CreateModuleRuntime(IModuleDefinition moduleDefinition)
        {
            if (moduleDefinition == null)
                throw new ArgumentNullException("moduleDefinition");

            // create new module runtime service
            var services = this.RuntimeServices
                .Where(p => p is IModuleRuntimeServiceGenerator)
                .Select(p =>
                {
                    Type serivceContractType = null;
                    var service = (p as IModuleRuntimeServiceGenerator).GetModuleRuntimeService(moduleDefinition, out serivceContractType);
                    return new Tuple<Type, IModuleRuntimeService>(serivceContractType, service);
                });
            // create module runtime
            var moduleRuntime = new ModuleRuntime(moduleDefinition, services);
            // return
            return moduleRuntime;
        }

        internal virtual ModuleRuntime CreateModuleRuntime<T>()
        {
            IModuleRepository moduleRepository = null;

            if (!this.TryGetService<IModuleRepository>(out moduleRepository))
                throw new InvalidOperationException("ModuleRepository not found");

            var definition = moduleRepository.GetModuleDefinition<T>();

            if (definition == null)
                throw new InvalidOperationException("ModuleRepository returns null");

            return this.CreateModuleRuntime(definition);
        }

        protected virtual void OnModuleLoaded(IModuleDefinition definition, IModule module)
        {
            foreach (var service in this.RuntimeServices)
                if (service is IModuleAware)
                    ((IModuleAware)service).OnModuleLoaded(definition, module);
        }

        /// <summary>
        /// Destory a module
        /// </summary>
        /// <typeparam name="T"></typeparam>
        protected virtual void DestroyModule<T>() where T : class
        {
            // TODO
        }

        /// <summary>
        /// Destroy module runtime
        /// </summary>
        /// <param name="runtime"></param>
        protected virtual void DestroyModuleRuntime(ModuleRuntime runtime)
        {
            // TODO
        }

        #endregion protected

        #region private

        /// <summary>
        /// All modules
        /// </summary>
        private readonly ThreadSafeDictionary<Type, IModule> _modules = new ThreadSafeDictionary<Type, IModule>();
        /// <summary>
        /// is runtime running
        /// </summary>
        private static int _running = 0;

        private readonly static Dictionary<Type, Func<IRuntimeService>> _necessaryServices = new Dictionary<Type, Func<IRuntimeService>>()
        {
            // config
            {typeof(IConfiguration), new Func<IConfiguration>(()=>ConfigurationManager.GetSection("snowdreamist") as IConfiguration)},

            // log
            {typeof(ILog), new Func<ILog>(()=>new Diagnostics.Log())},
            // others
            {typeof(IModuleRepository), new Func<IModuleRepository>(()=>new MEFModuleRepository())},
            {typeof(IModuleActivator), new Func<IModuleActivator>(()=> new MEFModuleActivator())},
            {typeof(IImportedPluginManager), new Func<IImportedPluginManager>(()=> new MEFImportPluginManager())},
        };

        private void LoadLifetimeModules()
        {
            foreach (var moduleDef in this.ModuleRepository.Modules)
                if (moduleDef.ActiveType == ModuleActiveType.LifetimeService)
                    this.LoadModuleCore(moduleDef);
        }

        #endregion private
    }
}